<!DOCTYPE HTML>
<html lang="zh_CN" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>Lithium 双引擎 v0.0.1</title>
        <meta name="robots" content="noindex" />
        <!-- Custom HTML head -->
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="favicon.svg">
        <link rel="shortcut icon" href="favicon.png">
        <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
        <link rel="stylesheet" href="css/print.css" media="print">
        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="fonts/fonts.css">
        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->
    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="intro.html"><strong aria-hidden="true">1.</strong> 简介</a></li><li class="chapter-item expanded "><a href="Upgrade.html"><strong aria-hidden="true">2.</strong> 从单引擎迁移</a></li><li class="chapter-item expanded "><a href="basic.html"><strong aria-hidden="true">3.</strong> 基本用法</a></li><li class="chapter-item expanded "><a href="web/web.html"><strong aria-hidden="true">4.</strong> 网页接口</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="web/dev-tools.html"><strong aria-hidden="true">4.1.</strong> 开发者工具</a></li><li class="chapter-item expanded "><a href="web/env.html"><strong aria-hidden="true">4.2.</strong> env</a></li><li class="chapter-item expanded "><a href="web/exit.html"><strong aria-hidden="true">4.3.</strong> exit</a></li><li class="chapter-item expanded "><a href="web/main-window.html"><strong aria-hidden="true">4.4.</strong> MainWindow 类</a></li><li class="chapter-item expanded "><a href="web/open-window.html"><strong aria-hidden="true">4.5.</strong> 打开窗口</a></li><li class="chapter-item expanded "><a href="web/plugins.html"><strong aria-hidden="true">4.6.</strong> 插件</a></li><li class="chapter-item expanded "><a href="web/struct-type.html"><strong aria-hidden="true">4.7.</strong> ctype</a></li><li class="chapter-item expanded "><a href="web/shell.html"><strong aria-hidden="true">4.8.</strong> 运行本地脚本</a></li><li class="chapter-item expanded "><a href="web/tray-icon.html"><strong aria-hidden="true">4.9.</strong> 系统托盘</a></li></ol></li><li class="chapter-item expanded "><a href="cmdline.html"><strong aria-hidden="true">5.</strong> 命令行参数</a></li><li class="chapter-item expanded "><a href="runtime.html"><strong aria-hidden="true">6.</strong> 运行时</a></li><li class="chapter-item expanded "><a href="crash-report.html"><strong aria-hidden="true">7.</strong> 崩溃报告</a></li><li class="chapter-item expanded "><a href="transparent-window.html"><strong aria-hidden="true">8.</strong> 透明窗口</a></li><li class="chapter-item expanded "><a href="preferences.html"><strong aria-hidden="true">9.</strong> Preferences 配置文件</a></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Lithium 双引擎 v0.0.1</h1>

                    <div class="right-buttons">
                        <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="简介"><a class="header" href="#简介">简介</a></h1>
<p><code>lithium engine</code> 是武汉锂钠氪锶科技有限公司提供的一款产口. 支持多个平台, 提供了
丰富的接口, 利用 web 技术高效开发本地应用.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="浏览器迁移过程"><a class="header" href="#浏览器迁移过程">浏览器迁移过程</a></h1>
<blockquote>
<p>新旧引擎使用差异。本篇对已经有使用单引擎的用户提供。</p>
</blockquote>
<p>尽管我们秉承着 &quot;不让已有用户修改代码&quot; 的原则，但是由于技术架构的现实原因导致不可避免的部分代码修改。我们已经最大限度的做了使用方式上的兼容。</p>
<ul>
<li>标记为 兼容 的，原则上不需要修改适配。此处提出是为了在时间资源充裕时推荐更规范的使用方式。</li>
</ul>
<h2 id="1兼容驼峰命名"><a class="header" href="#1兼容驼峰命名">1.[兼容]驼峰命名</a></h2>
<p>目前已经尽可能兼容了旧参数，原则上不需要修改(但是新开发的项目打算使用双引擎时仍然建议使用更规范的命名方式)</p>
<pre><code>keep_above=&gt;keepAbove,skip_bar=&gt;skipBar,begin_move=&gt;beginMove
</code></pre>
<h2 id="2兼容命令行参数"><a class="header" href="#2兼容命令行参数">2.[兼容]命令行参数</a></h2>
<p>原先使用一个横杠传递参数的，新引擎应使用两个横杠</p>
<blockquote>
<p>配置 LITHIUM_ENABLE_XPC_URLS 时，双引擎需要<code>;</code>分隔的单项至少是一个完整的域名，端口号是域名的一部分。</p>
</blockquote>
<h2 id="3小变动-自定义资源文件目录"><a class="header" href="#3小变动-自定义资源文件目录">3.[小变动] 自定义资源文件目录</a></h2>
<p>原先单引擎自定义的资源文件使用习惯不变，目录改为 ./gecko/</p>
<h2 id="4小变动-任务栏右键关闭事件绑定"><a class="header" href="#4小变动-任务栏右键关闭事件绑定">4.[小变动] 任务栏右键关闭事件绑定</a></h2>
<p>给原先绑定事件 由用户控制是否 <code>e.preventDefault();</code> 而双引擎中用户只需要返回 bool 值。</p>
<p>您还可以通过查看 手册 中的 <code>main-window</code> 一节 <code>onclose</code>  查看使用示例。</p>
<h2 id="5小变动背景透明设置"><a class="header" href="#5小变动背景透明设置">5.[小变动]背景透明设置</a></h2>
<p>单引擎版本背景色和透明选项可以一个参数设置好，双引擎版本必须使用两个参数来达到目的。</p>
<p>最终逻辑是，使用两个参数来达到控制背景色的目的，虽然Gecko会忽略是否透明的选项(仅根据背景色的透明值即可判断)</p>
<pre><code class="language-shell">--background-color='#fff'
--background-transparent
</code></pre>
<h2 id="6兼容lnksmainwindowwindow"><a class="header" href="#6兼容lnksmainwindowwindow">6.[兼容]lnks.MainWindow(window)</a></h2>
<p>在双引擎的Chrome中会忽略 window 参数。只能创建当前窗口的实例。 这本身不用调整什么，虽然不用传递该参数，但还是做了兼容允许传递该参数（代码不用改）。 需要注意的是，所有此前通过 返回得到 lnks.MainWindow(
window) 实例并加以控制窗口行为的都建议通过获取所有窗口实例的思路进行。</p>
<h2 id="7基本兼容托盘"><a class="header" href="#7基本兼容托盘">7.[基本兼容]托盘</a></h2>
<p><code>this.trayIcon = new lnks.TrayIcon(window, iconUrl);</code> 在双引擎的Chrome中会忽略 window 参数。 另外，支持了 <code>iconUrl</code>
参数可选，当不传递此参数时，将使用 <code>gecko/default.ico</code> 做为托盘图标。</p>
<p>事件绑定建议在托盘创建页面完成。代码完全兼容，只是以前建议由托盘菜单页面负责事件绑定，改为由创建页面负责事件绑定。</p>
<h2 id="8继续控制打开的子窗口子应用"><a class="header" href="#8继续控制打开的子窗口子应用">8.继续控制打开的子窗口(子应用)</a></h2>
<p>单引擎版本通过保存返回的窗口实例对象来继续操作窗口，双引擎无法使用相同方法。 双引擎推荐通过遍历所有窗口后得到指定窗口实例再加以控制的方法。 当前(2021.12.9)我们仍在努力兼容单引擎版本的返回窗口实例功能，使尽可能兼容。</p>
<h2 id="9新开窗口openwindow2"><a class="header" href="#9新开窗口openwindow2">9.新开窗口(openWindow2)</a></h2>
<p><code>lnks.openWindow2(win_args,user_args)</code></p>
<p>由于双引擎的技术局限性，在传递user_args时，只能传递可序列化后完整反序列化的对象（普通字段属性key=&gt;val的对象依然可用，不可以将一个 new
Window的窗口实例在此处传递，比如托盘实例，因为双引擎通信无法满足这个对象的目的，运行环境完全变了）。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="基本用法"><a class="header" href="#基本用法">基本用法</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="web-接口"><a class="header" href="#web-接口">Web 接口</a></h1>
<p>本章节描述的接口用于 web 页面中, 可由 JavaScript 代码来调用.</p>
<h2 id="lnks"><a class="header" href="#lnks">lnks</a></h2>
<p><code>window.lnks</code> 对象包含了 <code>lithium</code> 引擎提供的所有前端接口,
该对象的属性是只读的 (read-only).</p>
<p>尽管 <code>lnks</code> 提供了执行任意本地代码的功能, 但是 <code>lithium</code> 支持完整的浏览器安全标准,
默认情况下 <code>lnks</code> 对象不可用. 需要显式地为信任的代码所在地址配置白名单, 才能使用
<code>lnks</code> 提供的接口.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="开发者工具"><a class="header" href="#开发者工具">开发者工具</a></h1>
<p>使用 <code>Control + j</code> 调出 开发者工具。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="env"><a class="header" href="#env">env</a></h1>
<p><code>lnks.env</code> 对象用于读取或者更改引擎的环境变量.</p>
<h2 id="get"><a class="header" href="#get">get</a></h2>
<p><code>lnks.env.get(&quot;HOME&quot;)</code> 获取环境变量的定义值</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="exit"><a class="header" href="#exit">exit</a></h1>
<p><code>lnks.exit()</code> 关闭所有页面以及浏览器引擎.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="mainwindow-类"><a class="header" href="#mainwindow-类">MainWindow 类</a></h1>
<ul>
<li>MainWindow
<ul>
<li>
<p>exit() =&gt; os_exit()</p>
<pre><code class="language-javascript">lnks.exit() // 完全退出浏览器
</code></pre>
</li>
<li>
<p>plugins() =&gt; Cc[&quot;@mozilla.org/plugin/host;1&quot;].getService 用于获取插件列表，启用禁用插件</p>
<pre><code class="language-javascript">let pluginName='Suwell Reader Plugin';
let plugin=null;
function getPlugin() {
    let tags = lnks.plugins().getPluginTags();
    for (var i = 0; i &lt; tags.length; i++) {
        if (tags[i].name == pluginName) {
            plugin=tags[i];
        }
    }
    if (plugin) {
        console.log(&quot;loadPlugin check ok&quot;);
        document.getElementById(&quot;message&quot;).innerHTML = ''
            + 'Name: ' + plugin.name + '&lt;br /&gt;'
            + 'Des : ' + plugin.description + '&lt;br /&gt;'
            + 'FileName: ' + plugin.filename + '&lt;br /&gt;'
            + 'FullPath: ' + plugin.fullpath;
    } else {
        console.log(&quot;loadPlugin check error&quot;);
    }
}
getPlugin(); // 获取对应插件对象
plugin.enabledState=2;// 插件启用
plugin.enabledState=0;// 插件禁用
</code></pre>
</li>
<li>
<p>StructType(name, fields) =&gt; ctypes.StructType(name, ff)
<a href="web/./StructType.html">StructType</a></p>
</li>
<li>
<p>getIdleTime() =&gt; idleService.idleTime 用于获取客户机闲置时间，auto lock office web</p>
<pre><code class="language-javascript">setTimeout(()=&gt;{ console.log(lnks.getIdleTime())  }, 6000); // output: 5965 或接近值
</code></pre>
</li>
<li>
<p>reload()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.reload(); // 页面将会重新加载
</code></pre>
</li>
<li>
<p>get size() ({width:int,height:int})</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.size; // 将输出: Object { width: 800, height: 600 }
</code></pre>
</li>
<li>
<p>setSize(width:int,height:int)</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.setSize(810,610); // 窗口将应用新设置的尺寸
</code></pre>
</li>
<li>
<p>get position() ({x:int, y:int})</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.position; // 将输出：Object { x: 615, y: 352 }
</code></pre>
</li>
<li>
<p>setPosition({x:int, y:int})</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.setPosition(200,50); // 窗口将应用新设置的坐标位置
</code></pre>
</li>
<li>
<p>exceptionCert()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.exceptionCert(); // 将弹出添加例外证书的窗口
</code></pre>
</li>
<li>
<p>hasToplevelFocus() (0|1)</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
//w.has_toplevel_focus(); // 是否置顶的状态//旧版本兼容函数
w.hasToplevelFocus(); // 是否置顶的状态
</code></pre>
</li>
<li>
<p>fullscreen()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.fullscreen(); // 窗口将在全屏和非全屏状态之间切换
</code></pre>
</li>
<li>
<p>maximize()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.maximize(); // 窗口将在最大化和普通状态之间切换
</code></pre>
</li>
<li>
<p>normal()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.normal(); // 窗口状态将变换为 normal 状态。
</code></pre>
</li>
<li>
<p>minimize()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.minimize(); // 窗口状态将变换为 minimize 状态。
</code></pre>
</li>
<li>
<p>getWindowState() (state:string[&quot;minimized&quot;|&quot;maximized&quot;|&quot;normal&quot;|&quot;fullscreen&quot;])</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.windowState; // 将输出：normal,fullscreen,maximized,minimized 之间的一个值。
</code></pre>
</li>
<li>
<p>beginMove(ev)</p>
<pre><code class="language-html">&lt;div onmousedown=&quot;begin_move(event)&quot;&gt;移动示例&lt;/div&gt;
&lt;script language=&quot;javascript&quot;&gt;
if (window.lnks) {
  this.win = new window.lnks.MainWindow(window);
}
function begin_move(e) {
    //避免titlebar上的按钮点击事件被吃掉
    if (e.currentTarget != e.target) return false;
    //this.win.begin_move(e, this);//旧版本兼容函数
    this.win.beginMove(e, this);
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>setDesktopMode() 没有在用，已经和客户(15s)确认。</p>
</li>
<li>
<p>close close single window</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.close(); // 窗口将关闭。
</code></pre>
</li>
<li>
<p>get visibility () (bool)</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.visibility; // true | false
</code></pre>
</li>
<li>
<p>set visibility (bool) ()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.visibility = false; //窗口将隐藏
w.visibility = true; //窗口将可见
</code></pre>
</li>
<li>
<p>setFocus()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.setFocus(); // 窗口将获取焦点。
</code></pre>
</li>
<li>
<p>skipBar (bool) ()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.skip_bar(false); //窗口将出现在任务栏  兼容函数
w.skip_bar(true); //窗口将不会出现在任务栏 兼容函数
w.skipBar(false); //窗口将出现在任务栏
w.skipBar(true); //窗口将不会出现在任务栏
</code></pre>
</li>
<li>
<p>keepAbove (bool) ()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.keep_above(true); //窗口将保持置顶，不会被其它窗口盖住 兼容函数
w.keep_above(false); //窗口将取消置顶，可以被其它窗口盖住 兼容函数
w.keepAbove(true); //窗口将保持置顶，不会被其它窗口盖住
w.keepAbove(false); //窗口将取消置顶，可以被其它窗口盖住
</code></pre>
</li>
<li>
<p>setIcon(url: string)</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.setIcon(); //窗口将应用默认图标
w.setIcon(&quot;file:///home/pathToIcon/lnks.ico&quot;); //窗口将应用本地文件作为图标
w.setIcon(&quot;http(s)://urlToIcon/lnks.ico&quot;); //窗口将应用URL文件作为图标
w.setIcon(&quot;lnks.ico&quot;); //窗口将应用当前域名解析出的URL文件作为图标
</code></pre>
</li>
<li>
<p>setTitle 设置 title</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.set_title(&quot;测试标题一&quot;); //窗口将应用新设置的标题  兼容函数
w.setTitle(&quot;测试标题一&quot;); //窗口将应用新设置的标题
</code></pre>
</li>
<li>
<p>all()</p>
<pre><code class="language-javascript">// 返回当前所有的窗口对象
lnks.MainWindow.all();
let allWins = lnks.MainWindow.all();
allWins.forEach(w =&gt; {
    output(&quot;DOC 标题:&quot;+w.title, &quot;focusResult&quot;);
    output(&quot;聚焦状态:&quot;+w.hasToplevelFocus(), &quot;focusResult&quot;);
    output(&quot;聚焦状态:&quot;+w.has_toplevel_focus(), &quot;focusResult&quot;);
});
</code></pre>
</li>
<li>
<p>close()</p>
<pre><code class="language-javascript">let w = new lnks.MainWindow(window);
w.close(); // 窗口将关闭。
</code></pre>
</li>
<li>
<p>onclose()</p>
<pre><code class="language-javascript">// onclose 动作，目标在于实现 任务栏右键关闭窗口时(右上角也会触发) 误操作拦截提醒。
// 该函数绑定在 mainWindow 上，返回 true 则继续关闭，返回 false 则取消该动作。
let w = new lnks.MainWindow(window);
w.onclose = () =&gt; {
    return confirm(&quot;确定要退出吗?&quot;);
}
</code></pre>
</li>
<li>
<p>sendMessage(String)</p>
<pre><code class="language-javascript">// sendMessage 目标在于给指定窗口发送消息．用 onmessage 函数收取，详见　onmessage 函数章节
// 该函数在　mainWindow 上，无返回

// 给自己的窗口发消息
let w = new lnks.MainWindow(window);
w.sendMessage(&quot;这是一个消息&quot;);

// 给其它窗口发消息
// 查找其它窗口的函数
window.getLnksWin = (filterObj, update=false) =&gt; {
    if (window.lnks) {
        if (window.lnksWins) {
            for (const lnksWin of lnksWins) {
                console.log(`遍历窗口: title:${lnksWin.title}, appId:${lnksWin.appId}, url:${lnksWin.url}`)
                if (lnksWin.appId == filterObj.appId || lnksWin.url == filterObj.url || lnksWin.title == filterObj.title) {
                    return lnksWin
                }
            }
        }
        if (update) {
            window.lnksWins = lnks.MainWindow.all();
            return window.getLnksWin(filterObj, false);
        }
    }
    return null;
}
//获取其它窗口
let msgWin = getLnksWin({appId:receiveAppId}, true);
if (msgWin) {
  // 发送消息
  msgWin.sendMessage(msg);
} else {
  console.log(&quot;收消息的窗口不存在&quot;)
}
</code></pre>
</li>
<li>
<p>onmessage(funcHandle(msg){})</p>
<pre><code class="language-javascript">// onmessage 用来收取来自　sendMessage 发送的消息，sendMessage 详见 上一节

let w = new window.lnks.MainWindow(window);
w.onmessage = (msg) =&gt; {
  console.log(msg); // 每个窗口都是收取调用本窗口对象中　sendMessage 发送的消息
};
}
</code></pre>
</li>
<li>
<p>reloadIgnoreCache()</p>
<pre><code class="language-javascript">let w = new window.lnks.MainWindow(window);
w.reloadIgnoreCache() // 强制刷新页面,忽略缓存
// Shift+F5 也会触发同样的操作
</code></pre>
</li>
</ul>
</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="web-中请求打开窗口"><a class="header" href="#web-中请求打开窗口">Web 中请求打开窗口</a></h1>
<p><code>openWindow2(winArgs, userArgs)</code> 用于打开新的网页窗口.</p>
<h2 id="winargs-参数"><a class="header" href="#winargs-参数">winArgs 参数</a></h2>
<p><code>winArgs</code> 是一个 js object, 它支持的选项如下:</p>
<ul>
<li>left, 窗口距屏幕左侧多少个像素, 比如 <code>left: 100</code> 必须为数字</li>
<li>top, 窗口距屏幕顶部多少个像素, 比如 <code>top: 100</code> 必须为数字</li>
<li>width, 窗口宽度, 比如 <code>width: 800</code> 必须为数字</li>
<li>height, 窗口宽度, 比如 <code>height: 600</code> 必须为数字</li>
<li>alwaysOnTop, 将窗口置顶. 默认为 false</li>
<li>skipBar, 不在任务栏显示窗口. 默认为 false</li>
<li>hideChrome, 隐去窗口外边框和标题栏. 默认为 false</li>
<li>hidden, 默认不显示界面, 需要之后代码中调用 show() 方法才显示. 默认为 false</li>
<li>appId, 标记id</li>
<li>icon, 强制图标，优先级大于网页自身图标</li>
<li>title，窗口标题</li>
<li>winMode，窗口模式，比如 <code>normal|minimize|maximize|fullscreen</code></li>
<li>useGecko，使用 gecko 引擎打开链接, 默认为 false</li>
<li>reparent, 使用原chromium窗口嵌套gecko引擎</li>
<li>preload, 启用加载动画. 默认为 false</li>
<li>backgroundColor, 设置页面背景色</li>
<li>backgroundTransparent, 启用背景透明. 默认为 false. 要注意的是,
gecko 引擎可以控制某个页面是否透明, 而 chromium 引擎需要在引擎启动时显式地设置
全局的窗口都是背景透明的. 所以这个选项只对 gecko 中打开的窗口有效果</li>
</ul>
<h2 id="userargs"><a class="header" href="#userargs">userArgs</a></h2>
<p><code>userAgs</code> 可以传递参数给新打开的窗口, 它可以是字符串, 或者 JSON object.</p>
<p>新窗口中通过 <code>window.arguments</code> 来访问它.</p>
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<ul>
<li>openWindow2(win_args: {home: string,mode: string,appId: string,icon: string,title: string,skipBar:bool},user_args:any)</li>
</ul>
<pre><code class="language-javascript">lnks.openWindow2({
    &quot;url&quot;: &quot;https://www.baidu.com&quot;,
    &quot;title&quot;: &quot;应用商店&quot;,
    &quot;icon&quot;: &quot;./icon.ico&quot;, // 传递一个默认的页面图标，优先级低于页面自定义
    &quot;skipBar&quot;: true,
    &quot;width&quot;: 800, // or 100%
    &quot;height&quot;: 600, // or 100%
    &quot;fullscreen&quot;: true, // 预期是会盖住任务栏的
    &quot;mode&quot;: &quot;topBar&quot;, // 窗体模式 default|topBar
    &quot;winMode&quot;: &quot;maximized&quot;, // 窗口模式 normal,fullscreen,maximized,minimized 之间的一个值, 不传递此值时 默认为  normal
    &quot;positionX&quot;:0, // or g.x-(width/2),
    &quot;positionY&quot;:0, // or g.y-height,
    &quot;hideChrome&quot;: true, // 隐藏系统边框
    &quot;hidden&quot;: false, // 是否打开时先不显示
    &quot;appId&quot;: &quot;&quot;, // appId 客户需求，每一个打开的页面都被认定为一个不会重复的 appId , 由小数点、下划线、字母大小写及数字组成
}, { // 用户可以随意定义
    &quot;customeBtns&quot;: [{&quot;用户可以随意定义&quot;}],
});
</code></pre>
<p>同时还可以通过链式调用来对打开窗口的动作进行事件绑定</p>
<pre><code class="language-javascript">lnks.openWindow2({url:location.href}).onSuccess((win) =&gt; {
    console.log(&quot;openWindow2 success:&quot;, win);
}).onFailure((e) =&gt; {
    console.log(&quot;openWindow2 failure:&quot;, e);
})
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="插件"><a class="header" href="#插件">插件</a></h1>
<blockquote>
<p>本接口目前仅支持 gecko 引擎(针对npapi接口)</p>
</blockquote>
<p>用法</p>
<pre><code class="language-javascript">let tags = lnks.plugins().getPluginTags(); // 获取插件列表
let plugin = tags[0]; // 设定当前要操作的插件对象

plugin.enableState=2; // 启用插件
plugin.enableState=0; // 禁用插件

plugin.name; // 插件名字
plugin.description; // 插件描述
plugin.filename; // 插件文件名
plugin.fullpath; // 插件全路径
</code></pre>
<p>完整示例</p>
<pre><code class="language-html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
	&lt;head&gt;
		&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
		&lt;title&gt;suwell app&lt;/title&gt;
    	&lt;style&gt;
    	    #doc &gt; button{width:150px;}
    	&lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
		&lt;div&gt;
			&lt;button onclick=&quot;loadPlugin()&quot;&gt;插件加载&lt;/button&gt;     将读取并显示插件信息                &lt;br /&gt;
			&lt;button onclick=&quot;initPlugin()&quot;&gt;插件初始化&lt;/button&gt;   将确保启用插件，将可在 console 控制台 通过 navigator.plugins 获取到启用的插件 &lt;br /&gt;
			&lt;button onclick=&quot;newPlugin()&quot;&gt;插件显示&lt;/button&gt;      显示插件界面                &lt;br /&gt;
			&lt;button onclick=&quot;openFile()&quot;&gt;插件实例创建&lt;/button&gt;   将提示您选择要打开的具体文档，以显示实例内容界面        &lt;br /&gt;
			&lt;button onclick=&quot;disablePlugin()&quot;&gt;插件销毁&lt;/button&gt;  将禁用插件                    &lt;br /&gt;
			&lt;button onclick=&quot;delPlugin()&quot;&gt;插件删除&lt;/button&gt;      将在磁盘级别移除插件(需要注意权限)            &lt;br /&gt;
			&lt;button onclick=&quot;InitFrame()&quot;&gt;插件删除恢复&lt;/button&gt;  将在磁盘恢复移除的插件
		&lt;/div&gt;
        &lt;div id=&quot;message&quot;&gt;&lt;/div&gt;
		&lt;div id=&quot;pdf&quot;&gt;
	    &lt;/div&gt;
        &lt;script type=&quot;text/javascript&quot;&gt;
            let pluginName='Suwell Reader Plugin';
            let plugin=null;
            function check() {
                if (null === plugin) {
                    document.getElementById(&quot;message&quot;).innerHTML = '请先确认系统中已安装好插件，并在此处加载了插件';
                    return false;
                } else {
                    return true;
                }
            }
            function loadPlugin() {
                let tags = lnks.plugins().getPluginTags();
                for (var i = 0; i &lt; tags.length; i++) {
                    if (tags[i].name == pluginName) {
                        plugin=tags[i];
                    }
                }
                if (check()) {
                    console.log(&quot;loadPlugin check ok&quot;);
                    document.getElementById(&quot;message&quot;).innerHTML = ''
                        + 'Name: ' + plugin.name + '&lt;br /&gt;'
                        + 'Des : ' + plugin.description + '&lt;br /&gt;'
                        + 'FileName: ' + plugin.filename + '&lt;br /&gt;'
                        + 'FullPath: ' + plugin.fullpath;
                } else {
                    console.log(&quot;loadPlugin check error&quot;);
                }
            }
            function initPlugin() {
                if (check()) {
                    plugin.enabledState=2;// 插件启用
                    document.getElementById(&quot;message&quot;).innerHTML = '启用操作执行完毕';
                }
            }
            function newPlugin() {
                if (check()) {
                    document.getElementById(&quot;pdf&quot;).innerHTML='&lt;embed id=&quot;reader&quot; type=&quot;application/ofd&quot; width=&quot;1200&quot; height=&quot;900&quot;/&gt;';
                    document.getElementById(&quot;message&quot;).innerHTML = '显示操作执行完毕';
                }
            }
            function openFile() {
                if (check()) {
                    document.getElementById(&quot;message&quot;).innerHTML = '请通过点击界面中 &lt;font color=&quot;blue&quot;&gt;最左上角的按钮&lt;/font&gt; 选择 您要打开的文件';
                }
            }
            function disablePlugin() {
                if (check()) {
                    plugin.enabledState=0;// 插件禁用
                    document.getElementById(&quot;pdf&quot;).innerHTML='';
                    document.getElementById(&quot;message&quot;).innerHTML = '销毁操作执行完毕';
                }
            }
            function delPlugin() {
                window.lnks.$(`mv /opt/suwell /opt/suwell2`, ()=&gt;{}, ()=&gt;{});
                document.getElementById(&quot;message&quot;).innerHTML = '删除操作执行完毕';
            }
            function recoverPlugin() {
                window.lnks.$(`mv /opt/suwell2 /opt/suwell`, ()=&gt;{}, ()=&gt;{});
                document.getElementById(&quot;message&quot;).innerHTML = '恢复操作执行完毕';
            }
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;

</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="web-中使用-ctype"><a class="header" href="#web-中使用-ctype">Web 中使用 ctype</a></h1>
<blockquote>
<p>StructType(name, fields) =&gt; ctypes.StructType(name, ff)</p>
</blockquote>
<pre><code class="language-html">&lt;html manifest=&quot;test.manifest&quot;&gt;
  &lt;meta charset=&quot;utf-8&quot; /&gt;
  &lt;body
    contextmenu=&quot;test_menu&quot;
    style=&quot;background-color: white;font-size: 12px;&quot;
  &gt;
    &lt;menu type=&quot;context&quot; id=&quot;test_menu&quot;&gt;
      &lt;menuitem
        label=&quot;调试器&quot;
        onclick=&quot;lnks.DevTools.toggle(window)&quot;
      &gt;&lt;/menuitem&gt;
      &lt;menuitem label=&quot;刷新&quot; onclick=&quot;location.reload()&quot;&gt;&lt;/menuitem&gt;
    &lt;/menu&gt;
    &lt;pre id=&quot;info&quot;&gt;&lt;/pre&gt;
    &lt;script&gt;
      let ctypes = lnks.ctypes;

      //定义drm相关的数据类型，需要保证和/usr/include/xf86XX.h中保持一致。
      const drmVersion = lnks.StructType(&quot;drmVersion&quot;, [
        { version_major: &quot;ctypes.int&quot; },
        { version_minor: &quot;ctypes.int&quot; },
        { version_patchlevel: &quot;ctypes.int&quot; },

        { name_len: &quot;ctypes.int&quot; },
        { name: &quot;ctypes.char.ptr&quot; },

        { date_len: &quot;ctypes.int&quot; },
        { date: &quot;ctypes.char.ptr&quot; },

        { desc_len: &quot;ctypes.int&quot; },
        { desc: &quot;ctypes.char.ptr&quot; }
      ]);

      const drmModeModeInfo = lnks.StructType(&quot;drmModeModeInfo&quot;, [
        { clock: &quot;ctypes.uint32_t&quot; },

        { hdisplay: &quot;ctypes.uint16_t&quot; },
        { hsync_start: &quot;ctypes.uint16_t&quot; },
        { hsync_end: &quot;ctypes.uint16_t&quot; },
        { htotal: &quot;ctypes.uint16_t&quot; },
        { hskew: &quot;ctypes.uint16_t&quot; },

        { vdisplay: &quot;ctypes.uint16_t&quot; },
        { vsync_start: &quot;ctypes.uint16_t&quot; },
        { vsync_end: &quot;ctypes.uint16_t&quot; },
        { vtotal: &quot;ctypes.uint16_t&quot; },
        { vscan: &quot;ctypes.uint16_t&quot; },

        { vrefresh: &quot;ctypes.uint32_t&quot; },

        { flags: &quot;ctypes.uint32_t&quot; },
        { type: &quot;ctypes.uint32_t&quot; },

        { name: &quot;ctypes.char.ptr&quot; }
      ]);

      const drmModeConnector = lnks.StructType(&quot;drmModeConnector&quot;, [
        { connector_id: &quot;ctypes.uint32_t&quot; },
        { encoder_id: &quot;ctypes.uint32_t&quot; },
        { connector_type: &quot;ctypes.uint32_t&quot; },
        { connector_type_id: &quot;ctypes.uint32_t&quot; },

        { connection: &quot;ctypes.int&quot; },

        { mmWidth: &quot;ctypes.uint32_t&quot; },
        { mmHeight: &quot;ctypes.uint32_t&quot; },

        { drmModeSubPixel: &quot;ctypes.int&quot; },

        { count_modes: &quot;ctypes.int&quot; },
        { modes: &quot;ctypes.void_t.ptr&quot; },

        { count_props: &quot;ctypes.int&quot; },
        { props: &quot;ctypes.uint32_t.ptr&quot; },
        { props_values: &quot;ctypes.uint64_t.ptr&quot; },

        { count_encoders: &quot;ctypes.int&quot; },
        { encoders: &quot;ctypes.uint32_t.ptr&quot; }
      ]);

      const drmModeRes = lnks.StructType(&quot;drmModeRes&quot;, [
        { count_fbs: &quot;ctypes.int&quot; },
        { fbs: &quot;ctypes.PointerType(ctypes.uint32_t)&quot; },

        { count_crtcs: &quot;ctypes.int&quot; },
        { crtcs: &quot;ctypes.PointerType(ctypes.uint32_t)&quot; },

        { count_connectors: &quot;ctypes.int&quot; },
        { connectors: &quot;ctypes.PointerType(ctypes.uint32_t)&quot; },

        { count_encoders: &quot;ctypes.int&quot; },
        { encoders: &quot;ctypes.PointerType(ctypes.uint32_t)&quot; },

        { min_width: &quot;ctypes.uint32_t&quot; },
        { max_width: &quot;ctypes.uint32_t&quot; },
        { min_height: &quot;ctypes.uint32_t&quot; },
        { max_height: &quot;ctypes.uint32_t&quot; }
      ]);

      //打开libc.so
      var libc = ctypes.open(&quot;libc.so.6&quot;);

      //打开libdrm.so
      var libdrm = ctypes.open(&quot;libdrm.so&quot;);

      // 申明需要使用到的函数列表
      let libc_open = libc.declare(
        &quot;open&quot;,
        ctypes.default_abi,
        ctypes.int,
        ctypes.char.ptr,
        ctypes.int
      );

      let drmGetRenderDeviceNameFromFd = libdrm.declare(
        &quot;drmGetRenderDeviceNameFromFd&quot;,
        ctypes.default_abi,
        ctypes.char.ptr,
        ctypes.int
      );
      let drmModeGetResources = libdrm.declare(
        &quot;drmModeGetResources&quot;,
        ctypes.default_abi,
        drmModeRes.ptr,
        ctypes.int
      );
      let drmGetVersion = libdrm.declare(
        &quot;drmGetVersion&quot;,
        ctypes.default_abi,
        drmVersion.ptr,
        ctypes.int
      );
      let drmModeGetConnector = libdrm.declare(
        &quot;drmModeGetConnector&quot;,
        ctypes.default_abi,
        drmModeConnector.ptr,
        ctypes.int,
        ctypes.int
      );

      let INFO = {};

      //打开DRM设备，若有多张显卡需要调整路径。
      let fd = libc_open(&quot;/dev/dri/card0&quot;, 2); //O_RDWR

      function readString(v) {
        let stringVal = &quot;&quot;;
        if (v &amp;&amp; v.contents) {
          stringVal = v.readString();
        }
        return stringVal;
      }

      INFO[&quot;DRM_FD&quot;] = fd;
      INFO[&quot;渲染设备名称&quot;] = readString(drmGetRenderDeviceNameFromFd(fd));

      let ver = drmGetVersion(fd).contents;
      INFO[&quot;DRM版本名称&quot;] = readString(ver.name);
      INFO[&quot;DRM版本日期&quot;] = readString(ver.date);
      INFO[&quot;DRM厂商&quot;] = readString(ver.desc);

      //获取mode相关信息
      let allRes = drmModeGetResources(fd).contents;

      function toIntArray(v, len) {
        let r = [];
        let tmp = ctypes.cast(v, ctypes.uint32_t.array(len).ptr).contents;
        for (let i = 0; i &lt; len; i++) {
          r.push(tmp[i]);
        }
        return r;
      }
      let connectors = toIntArray(allRes.connectors, allRes.count_connectors);
      let crtcs = toIntArray(allRes.crtcs, allRes.count_crtcs);
      let encoders = toIntArray(allRes.encoders, allRes.count_encoders);
      INFO[&quot;连接器&quot;] = connectors;
      INFO[&quot;控制器&quot;] = crtcs;
      INFO[&quot;编码器&quot;] = encoders;

      //输出connector的详细信息。
      function dump_connector_info(conn) {
        INFO[&quot;当前使用的连接器ID:&quot;] = conn.connector_id;
        INFO[&quot;编码器ID&quot;] = conn.encoder_id;

        let mode = ctypes.cast(conn.modes, drmModeModeInfo.ptr).contents;
        INFO[&quot;物理分辨率&quot;] = {
          宽: mode.hdisplay,
          高: mode.vdisplay
        };
        INFO[&quot;物理尺寸&quot;] = {
          宽: conn.mmWidth,
          高: conn.mmHeight
        };
      }

      for (let i = 0; i &lt; allRes.count_connectors; i++) {
        let conn = drmModeGetConnector(fd, connectors[i]).contents;

        //当前连接上的connector且mode数量大于0。(也就是当前正在使用的显示器)
        if (conn.connection == 1 &amp;&amp; conn.count_modes &gt; 0) {
          dump_connector_info(conn);
        }
      }

      document.getElementById(&quot;info&quot;).innerText = JSON.stringify(
        INFO,
        null,
        &quot; &quot;
      );
      if (window.lnks) {
        win = new window.lnks.MainWindow(window);
        let width = 600;
        let height = 700;
        win.setSize(width, height);
      }
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;

</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="web-中运行本地脚本"><a class="header" href="#web-中运行本地脚本">Web 中运行本地脚本</a></h1>
<p><code>lnks.$(command, stdoutFn, stderrFn)</code></p>
<ul>
<li>lnks.$(shellcode, [optional]stdoutCallback, [optional]stderrCallback) 返回一个Promise，
resolved时传递当前未处理过的stdout内容。</li>
</ul>
<pre><code class="language-javascript">lnks.$(&quot;ls -lah&quot;, console.log, console.error);
</code></pre>
<pre><code class="language-javascript">lnks.$(`unzip -o -d ${dest} ${temp}`)
    .then(resolve) //若成功则resolved
    .catch(reject);
</code></pre>
<p>该函数是异步执行的，不会阻塞后续的操作。会根据程序错误码返回错误。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="系统托盘"><a class="header" href="#系统托盘">系统托盘</a></h1>
<h2 id="新引擎有关托盘的使用逻辑"><a class="header" href="#新引擎有关托盘的使用逻辑">新引擎有关托盘的使用逻辑</a></h2>
<p>主窗口创建托盘，然后绑定托盘事件。
托盘事件的绑定由用户自定义，一般使用场景是：左键点击控制主窗口是的显示隐藏，右键点击控制右键菜单窗口的显示隐藏。
右键菜单窗口由用户定义确认，本质上和其它窗口页面的创建和使用没有区别。</p>
<p>新引擎都是通过 lnks.MainWindow.all(); 获取所有窗口后，遍历出自己所需的窗口而后加以操作的，这种方式是由<code>chrome</code> 引擎以及<code>双引擎</code>窗口关系结构决定的。</p>
<p>创建托盘时遵循以下原则：
1、<code>new lnks.TrayIcon(&quot;IconAccessPath&quot;)</code> 中 <code>IconAccessPath</code> 是可选参数，不传递该参数时，使用默认图标(gecko/default.ico)。
2、传递 <code>IconAccessPath</code> 时，引擎会先下载图标，下载过程会阻塞后续的程序运行。下载失败则不会创建托盘，下载如果需要 10 秒则需要 10 秒后才创建托盘。
3、传递 图标 <code>Base64</code> 数据。新引擎提供了该托盘创建方式，使用该方式可以达到网络异常和本地不可定制之间的平衡。</p>
<h2 id="双引擎托盘创建实例"><a class="header" href="#双引擎托盘创建实例">双引擎托盘创建实例</a></h2>
<p>待功能实现后补充完整</p>
<pre><code class="language-javascript">// 托盘创建
this.trayIcon = new window.lnks.TrayIcon(window, iconUrl);
// 托盘事件绑定
this.trayIcon.onMenuPopup = () =&gt; console.log(`onMenuPopup()`);
this.trayIcon.onActivate = () =&gt; console.log(`onActivate()`);
// 托盘销毁
this.trayIcon.destroy();
this.trayIcon = null;
// 托盘位置获取(用以辅助控制右键菜单窗口的位置)
let geometry = this.trayIcon.geometry;
console.log(&quot;geometry:&quot;, geometry);
</code></pre>
<p>由于部分操作系统在激活前无法获取到托盘所在位置，因此强烈建议您在做依赖托盘坐标的动作时，与托盘的点击(激活)事件进行绑定。</p>
<p>如下进行右键菜单的操作：</p>
<pre><code class="language-javascript">// 创建托盘
window.trayIcon = new lnks.TrayIcon(window); // chromium has no remote ico feat
let g = window.trayIcon.geometry;
let width = 100;
let height = 210;
console.log(&quot;g:&quot;, g);
console.log(&quot;p:&quot;, g.x - 100 / 2, g.y - height);
let winArg = {
  url: &quot;./apps/popup.html&quot;,
  appId: &quot;trayWin&quot;,
  skipBar: true,
  width: width,
  height: height,
  // &quot;positionX&quot;: 0 - width,
  // &quot;positionY&quot;: 0 - height,
  hideChrome: true,
  winMode: &quot;normal&quot;,
  hidden: true,
  useGecko: win.isGecko()
};
// 创建托盘菜单页面
lnks.openWindow2(winArg, {});
console.log(`lnks.openWindow2(${JSON.stringify(winArg)}, {});`);

// 绑定鼠标左键点击事件
trayIcon.onActivate = () =&gt; {
  console.log(&quot;activate&quot;);
  window.win.visibility = !window.win.visibility;
};
// 绑定鼠标右键点击事件(点击时获取坐标)
trayIcon.onMenuPopup = () =&gt; {
  if (!window.trayWin) {
    window.trayWin = getLnksWin({appId: &quot;trayWin&quot;}, true);
  }
  let height=200
  console.log(&quot;size1:&quot;, JSON.stringify(trayWin.size));
  console.log(&quot;tray.visibily:&quot;, JSON.stringify(trayWin.visibily));
  if (trayWin.visibility) {
    //trayWin.setSize(1,1)//UOS设置无效
    trayWin.visibility = false;
    console.log(&quot;size2:&quot;, JSON.stringify(trayWin.size));
  } else {
    // UOS 系统没有显示时不能改位置，显示时可以改位置但是不能超出屏幕
    let g = this.trayIcon.geometry;
    console.log(&quot;g:&quot;, JSON.stringify(g));
    // devicePixelRatio 屏幕缩放比例
    let px = parseInt((g.x - 100/2)/devicePixelRatio);
    let py = parseInt((g.y - height)/devicePixelRatio);
    console.log(&quot;p:&quot;, px, py);
    trayWin.setSize(1,1);
    trayWin.visibility = true;
    trayWin.setPosition(px, py);
    trayWin.setSize(100,200)
  }
};
</code></pre>
<h2 id="老引擎的托盘创建实例"><a class="header" href="#老引擎的托盘创建实例">老引擎的托盘创建实例</a></h2>
<p>主页面 主动创建系统托盘</p>
<pre><code class="language-javascript">// 初始化一个托盘图标
this.trayIcon = new lnks.TrayIcon(window, &quot;./nazha.ico&quot;); // 一个托盘实例对象
this.lnksWin = new window.lnks.MainWindow(window); // 主窗口当前对象

// 创建托盘菜单页
// setTimeout 不是必须的
// 在有些运算力弱的机器上 初始化托盘图标可能需要一点时间。
setTimeout(() =&gt; {
  let g = this.trayIcon.geometry; // 获取创建好的托盘位置
  let width = 100;
  let height = 200;
  console.log(&quot;g:&quot;, g);
  console.log(&quot;p:&quot;, g.x - width / 2, g.y - height); // 根据托盘位置计算页面位置
  lnks.openWindow2(
    {
      url: &quot;./popup.html&quot;,
      // &quot;skip_bar&quot;: true,
      skipBar: true,
      width: width,
      height: height,
      positionX: g.x - width / 2,
      positionY: g.y - height,
      hideChrome: true,
      hidden: true
    },
    {
      // &quot;HomeWindow&quot;: this.lnksWin, // 打开页面的 window.arguments.HomeWindow
      // &quot;TrayIcon&quot;: this.trayIcon, // 打开页面的 window.arguments.TrayIcon
    }
  );
}, 1000);

// 销毁托盘
function delTray() {
  if (this.trayIcon) {
    // 确认创建托盘实例对象
    this.trayIcon.destroy();
  } else {
    console.error(&quot;TrayIcon not created&quot;);
  }
}
</code></pre>
<p>托盘右键菜单页</p>
<pre><code class="language-html">&lt;lnks_app&gt;&lt;/lnks_app&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;托盘&lt;/title&gt;
  &lt;/head&gt;
  &lt;style&gt;
    * {
      margin: 0;
      padding: 0;
    }
    .tray-container {
      background: rgba(0, 0, 0 0);
      width: 100%;
      position: absolute;
      bottom: 0;
    }
    .tray-container button {
      width: 100%;
      height: 30px;
      padding: auto;
      margin: auto;
      text-align: center;
    }
  &lt;/style&gt;
  &lt;body&gt;
    &lt;div class=&quot;tray-container&quot;&gt;
      &lt;button onclick=&quot;location.reload()&quot;&gt;刷新&lt;/button&gt;
      &lt;button onclick=&quot;lnks.DevTools.toggle(window)&quot;&gt;调试&lt;/button&gt;
      &lt;button onclick=&quot;lnks.exit()&quot;&gt;退出程序&lt;/button&gt;
    &lt;/div&gt;
  &lt;/body&gt;
  &lt;script&gt;
    window.onload = function () {
      if (window.lnks &amp;&amp; window.arguments) {
        // 获取主窗口Window实例
        let hw = window.arguments.HomeWindow; // 打开本窗口时传递过来的参数
        let tray = window.arguments.TrayIcon; // 打开本窗口时传递过来的参数
        this.win = new window.lnks.MainWindow(window); // 当前右键菜单窗口实例化对象
        win.setSize(100, 250);
        win.skip_bar(); //本窗口跳过任务栏
        win.keep_above(); //保持置顶
        win.visibility = false; //初始化时隐藏界面
        window.onclose = (e) =&gt; {
          var r = confirm(&quot;确定要退出吗?&quot;);
          if (r) {
            //nothing
          } else {
            e.preventDefault();
          }
        };
        onblur = (e) =&gt; {
          //失去焦点时隐藏本窗口
          console.info(&quot;失去焦点:onblur&quot;);
          win.visibility = false;
          return true;
        };
        onclick = () =&gt; {
          //任何点击事件后都隐藏窗口，这个可以根据应用逻辑自行调整。
          console.info(&quot;点击事件:onclick&quot;);
          win.visibility = false;
          return true;
        };

        //trayicon的鼠标右键处理逻辑
        tray.onMenuPopup = (a, b) =&gt; {
          console.info(&quot;右键:onMenuPopup:a&quot;, a);
          console.info(&quot;右键:onMenuPopup:b&quot;, b);
          console.info(&quot;右键:onMenuPopup:b&quot;, this.win.visibility);
          if (!this.win.visibility) {
            this.win.visibility = true;
            window.focus(); //强制获取焦点
          } else {
            this.win.visibility = false;
          }
        };

        if (hw) {
          //主窗口关闭或刷新时候销毁trayicon以及关闭本窗口。
          hw.win.addEventListener(&quot;unload&quot;, function (tray) {
            if (tray) {
              tray.destroy();
            }
            this.win.close();
          });
        }

        //trayicon的鼠标左键处理逻辑
        tray.onActivate = function (a, b) {
          console.info(&quot;左键:onActivate:a&quot;, a);
          console.info(&quot;左键:onActivate:b&quot;, b);
          if (hw) {
            hw.visibility = true;
            hw.minimize();
            hw.win.focus(); // win7 激活的窗口没有出现在最顶层
          } else {
            this.win.minimize();
          }
        };
      } else {
        alert(&quot;浏览器不支持本Demo&quot;);
      }
    };
  &lt;/script&gt;
&lt;/html&gt;
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="命令行选项"><a class="header" href="#命令行选项">命令行选项</a></h1>
<p>要注意与 <code>common/client_switches.h</code> 保持同步.</p>
<ul>
<li>icon, 设置窗口图标的链接, 比如 <code>--icon=file:///usr/share/lithium/icons/gedit.png</code> 不传递时使用 <code>gecko/default.ico</code></li>
<li>title, 设置窗口标题</li>
<li>transparent background</li>
<li>top bar</li>
</ul>
<h2 id="窗口相关的参数"><a class="header" href="#窗口相关的参数">窗口相关的参数</a></h2>
<ul>
<li>left, 窗口距屏幕左侧多少个像素, 比如 <code>--left=100</code></li>
<li>top, 窗口距屏幕顶部多少个像素, 比如 <code>--top=100</code></li>
<li>width, 窗口宽度, 比如 <code>--width=800</code>,<code>--width=100%</code>,</li>
<li>height, 窗口宽度, 比如 <code>--height=600</code>,<code>--height=100%</code>,</li>
<li>always-on-top, 将窗口置顶</li>
<li>skip-bar, 不在任务栏显示窗口</li>
<li>hide-chrome, 隐去窗口外边框和标题栏</li>
<li>hidden, 默认不显示界面, 需要之后代码中调用 show() 方法才显示</li>
<li>app-id, 标记id</li>
<li>icon, 强制图标，优先级大于网页自身图标</li>
<li>title，窗口标题</li>
<li>win-mode，窗口模式，比如 <code>normal|minimize|maximize|fullscreen</code></li>
<li>use-gecko，使用 gecko 引擎打开链接</li>
<li>user-args，用户自定义参数</li>
<li>force-no-check-certificate，忽略证书错误</li>
<li>disable-gpu，禁用 gpu</li>
<li>background-color, 背景颜色, TODO(Shaohua): 支持的颜色格式待补充</li>
<li>background-transparent, 背景透明</li>
<li>preload, 启用加载动画</li>
<li>default-title, 设置默认标题, 默认为空白字符串. 如果网页内没有设置标题, 将使用该值</li>
<li>send-message, 给指定的窗口(此参数必须同时和　app-id 一起使用)发送消息，比如　<code>--app-id=xxx --send-message=xxx</code></li>
</ul>
<h2 id="访问浏览器定制资源页面"><a class="header" href="#访问浏览器定制资源页面">访问浏览器定制资源页面</a></h2>
<p>自行编写变存放在 <code>lithium/gecko/*.html</code> 的静态页面资源文件可以通过内置协议访问: <code>lnks://inner/*.html</code></p>
<blockquote>
<p>出于安全考虑，此功能不支持子路径。
访问此内置资源不用特别定义特权路径，会自带特权。
比如在目录存放 <code>lithium/gecko/start.html</code> 页面，可以通过 <code>lnks://inner/start.html</code> 访问</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="运行时"><a class="header" href="#运行时">运行时</a></h1>
<ul>
<li>F5 to reload page</li>
<li>ctrl+j to open web inspector</li>
<li>ctrl+t to open web inspector for top bar</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="崩溃报告"><a class="header" href="#崩溃报告">崩溃报告</a></h1>
<p>目前支持生成 Breakpad/Crashpad 格式的崩溃文件.</p>
<p>不同的进程, 其生成的崩溃文件位置也不一样, 可以在这两个地方查找:</p>
<ul>
<li>/tmp/chromium-renderer-xxxx.dmp</li>
<li>$HOME/.lnks/lithium2/CrashReport/xxx.dmp</li>
</ul>
<h2 id="自动上传崩溃报告"><a class="header" href="#自动上传崩溃报告">自动上传崩溃报告</a></h2>
<p>如果浏览器引擎出错, 就会读取 <code>crash_reporter.cfg</code> 文件中定义的
<code>ServerURL</code> 属性, 它指定了上传服务器的地址, 默认值为空, 即表示禁止上传.</p>
<p>可以根据需要修改成本机可访问的服务器地址, 比如:
<code>http://localhost:8080/crash-report</code>,</p>
<p>同时还需要一个收集报告的服务器, <code>crash_server.py</code> 是一个简单的示例服务,
使用也很简单:</p>
<pre><code class="language-bash">python3 crash_server.py 8080 crash-report
</code></pre>
<p>这里的 <code>8080</code> 指定了监听的端口号, <code>crash-report</code> 指定保存的目录名.</p>
<p>完成以上的操作后, 就可以在服务器端自动收集客户机器上的崩溃报告了.</p>
<h2 id="如何查看崩溃文件"><a class="header" href="#如何查看崩溃文件">如何查看崩溃文件</a></h2>
<p>使用以下命令:</p>
<pre><code class="language-bash">minidump_stackwalk SYMBOL.dmp ./symbols
</code></pre>
<p>其中 <code>SYMBOL.dmp</code> 是收集到的崩溃记录, 而 <code>symbols</code> 目录是编译时生成的调试符号</p>
<h2 id="如何维护调试符号"><a class="header" href="#如何维护调试符号">如何维护调试符号</a></h2>
<p>第一步就是生成调试符号: 在编译完 lithium-frontend 项目之后, 需要从 <code>lithium</code> 可执行
文件中提取出调试符号, 这一操作可以用 <code>breakpad</code> 提供的 <code>dump_syms</code> 命令完成.</p>
<ol>
<li>
<p>下载 breakpad: <code>git clone -b rusty https://github.com/RustyProject/breakpad</code>
使用 rusty 分支, 该分支支持 cmake 工具.</p>
</li>
<li>
<p>编译这个项目: </p>
</li>
</ol>
<pre><code class="language-bash">cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
</code></pre>
<p>编译成功之后, 会在 <code>build</code> 目录生成 <code>dump_syms</code> 可执行文件</p>
<ol start="3">
<li>
<p>将 <code>dump_syms</code> 安装到 <code>PATH</code> 变量可访问的路径中, 以备调用</p>
</li>
<li>
<p>在编译完成 <code>lithium</code> 项目后, 调用 <code>dump_syms</code> 命令行成对应的 <code>lithium.sym</code> 调试文件:</p>
</li>
</ol>
<pre><code class="language-bash">dump_syms build/src/lithium &gt; build/src/lithium.sym
</code></pre>
<ol start="5">
<li>上传调试符号, 使用以下命令:</li>
</ol>
<pre><code class="language-bash">./tools/upload-symbol.sh ./build/src/lithium.sym
</code></pre>
<p>目前, 用于托管 symbol 文件的服务器在本地网络中, 还不保证它一直可用, 如果上传失败了,
联系我.</p>
<h2 id="何时需要生成调试符号"><a class="header" href="#何时需要生成调试符号">何时需要生成调试符号</a></h2>
<p>每次发正式版的安装包时, 都应该确认该包生成了对应的调试符号并且成功上传.</p>
<p>目常开发时, 不应该上传它们!</p>
<h2 id="测试"><a class="header" href="#测试">测试</a></h2>
<p>打开以下链接可以测试不同进程的崩溃:</p>
<ul>
<li>主进程 crash: <code>chrome://inducebrowsercrashforrealz</code></li>
<li>Renderer 进程 crash: <code>chrome://crash</code></li>
<li>GPU 进程 crash: <code>chrome://gpucrash</code></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="如果启用-chromium-透明窗口"><a class="header" href="#如果启用-chromium-透明窗口">如果启用 chromium 透明窗口</a></h1>
<blockquote>
<p>透明窗口在 chromium 是一项实验性功能，只是还不完整
具体表现是：目前只能对所有窗口的透明状态进行设置，不能对单个窗口状态进行设置</p>
</blockquote>
<p>默认情况下, 网页窗口都会有一个背景色. 如果需要绘制透明的网页, 需要单独进行配置.
而且这样的配置只能在浏览器启动时以命令行参数的方法指定.</p>
<p>启动浏览器时传入以下参数:</p>
<ul>
<li><code>--background-transparent</code> 用于启用背景透明</li>
<li><code>--use-gl=desktop</code>, 替换默认的 egl 模式</li>
</ul>
<p>如果指定了 <code>--use-gl=desktop</code> 后窗口依然不透明, 可以将这个选项换成:</p>
<ul>
<li><code>--disable-gpu</code>, 禁用 GPU 渲染</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="prefererencesjson-文件"><a class="header" href="#prefererencesjson-文件">prefererences.json 文件</a></h1>
<p>该文件可以设置部分特性.</p>
<p>目前支持的有:</p>
<ul>
<li><code>general.useragent.override</code>, 设置 User-Agent</li>
</ul>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
            </nav>

        </div>

        <script type="text/javascript">
            window.playground_copyable = true;
        </script>
        <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
        <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->
        <script type="text/javascript">
        window.addEventListener('load', function() {
            window.setTimeout(window.print, 100);
        });
        </script>
    </body>
</html>
